package cn.sgjk.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.sgjk.train.business.domain.*;
import cn.sgjk.train.business.enums.RedisKeyEnum;
import cn.sgjk.train.business.mapper.cust.SkTokenMapperCust;
import cn.sgjk.train.common.exception.BusinessException;
import cn.sgjk.train.common.exception.BusinessExceptionEnum;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import cn.sgjk.train.common.resp.PageResp;
import cn.sgjk.train.common.util.SnowUtil;
import cn.sgjk.train.business.mapper.SkTokenMapper;
import cn.sgjk.train.business.req.SkTokenQueryReq;
import cn.sgjk.train.business.req.SkTokenSaveReq;
import cn.sgjk.train.business.resp.SkTokenQueryResp;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SkTokenService {

    private static final Logger LOG = LoggerFactory.getLogger(SkTokenService.class);

    @Resource
    private SkTokenMapper skTokenMapper;

    @Autowired
    private SkTokenMapperCust skTokenMapperCust;

    @Autowired
    private DailyTrainSeatService dailyTrainSeatService;

    @Autowired
    private DailyTrainStationService dailyTrainStationService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${spring.profiles.active}")
    private String env;



    public void genDaily(Date date, String trainCode) {
        LOG.info("生成日期【{}】车次【{}】的令牌信息开始", DateUtil.formatDate(date), trainCode);

        //删除已有当天数据
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.createCriteria()
                .andDateEqualTo(date)
                .andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(skTokenExample);

        // 生成该车次的数据
        DateTime now = new DateTime();
        SkToken skToken = new SkToken();
        skToken.setId(SnowUtil.getSnowflakeNextId());
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);
        skToken.setDate(date);

        // 座位数
        int seatCount = dailyTrainSeatService.countSeat(date, trainCode);
        LOG.info("车次{}座位数：{}",trainCode,seatCount);

        long stationCount = dailyTrainStationService.countByTrain(trainCode,date);
        LOG.info("车次{}站数：{}",trainCode,stationCount);

        //  3/4需要根据实际卖票比例来定，一趟火车最多可以卖(seatCount * stationCount) 张火车票，在一票一站情况下
        int count = (int)(seatCount * stationCount * 3 / 4);
        skToken.setCount(count);

        skTokenMapper.insert(skToken);


    }

    public void save(SkTokenSaveReq req) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
        if (ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(SnowUtil.getSnowflakeNextId());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        } else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<SkToken> skTokenList = skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);

        PageResp<SkTokenQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }

    /**
     *  获取令牌
     */

    public boolean validSkToken(Date date,String trainCode,Long memberId) {
        LOG.info("会员{}获取日期{}车次{}的令牌开始",memberId,DateUtil.formatDate(date),trainCode);

        if(!env.equals("dev")) {
            try {
                // 先获取令牌锁，再校验令牌余量，防止机器人抢票，lockKey就是令牌，用来表示【谁能做什么】
                String lockKey = RedisKeyEnum.SK_TOKEN + "-" + DateUtil.formatDate(date) + "-" + trainCode + "-" + memberId;
                RLock lock = redissonClient.getLock(lockKey);
                boolean tryLock = lock.tryLock(0, 2, TimeUnit.SECONDS);

                if (Boolean.TRUE.equals(tryLock)) {
                    LOG.info("恭喜，抢到令牌锁了，lockKey：{}", lockKey);
                } else {
                    LOG.info("很遗憾，未抢到令牌锁了，lockKey：{}", lockKey);
                    return false;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        String skTokenCountKey = RedisKeyEnum.SK_TOKEN_COUNT+ "-" +
                DateUtil.formatDate(date) + "-" + trainCode;
        Object skTokenCount = redisTemplate.opsForValue().get(skTokenCountKey);
        // 缓存有直接获取令牌，否则访问数据库
        if(ObjectUtil.isNotNull(skTokenCount)) {
            LOG.info("缓存中有该车次的令牌大闸的key:{}", skTokenCountKey);
            Long count = redisTemplate.opsForValue().decrement(skTokenCountKey,1);
            //重新刷新缓存，减少过期次数，对数据库的穿透访问
            redisTemplate.expire(skTokenCountKey,60,TimeUnit.SECONDS);

            if(count < 0L){
                LOG.info("获取令牌失败：{}",skTokenCountKey);
                throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
            }else {
                LOG.info("获取令牌后，令牌余数:{}",count);
                // 每获取5个令牌更新一次数据库
                if(count % 3 == 0) {
                    LOG.info("更新数据库，令牌余数:{}",count);
                    skTokenMapperCust.decrease(date, trainCode,3L);
                }
                return true;
            }
        }else {

            LOG.info("缓存中没有该车次的令牌大闸的key:{}", skTokenCountKey);
            // 检查是否有令牌
            SkTokenExample skTokenExample = new SkTokenExample();
            skTokenExample.setOrderByClause("id desc");
            SkTokenExample.Criteria criteria = skTokenExample.createCriteria();
            criteria.andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> tokenList = skTokenMapper.selectByExample(skTokenExample);

            if(CollUtil.isEmpty(tokenList)) {
                LOG.info("找不到日期{}车次的{}的令牌",DateUtil.formatDate(date),trainCode);
                return false;
            }

            SkToken skToken = tokenList.get(0);
            if(skToken.getCount() <= 0) {
                LOG.info("日期{}车次{}的令牌余量为0",DateUtil.formatDate(date),trainCode);
                return false;
            }

            // 令牌还有余量
            // 令牌余数 -1
            Integer count = skToken.getCount() - 1;
            skToken.setCount(count);
            LOG.info("将该车次令牌放入缓存,key：{},count：{}",skTokenCountKey,count);
            redisTemplate.opsForValue().set(skTokenCountKey,count,60,TimeUnit.SECONDS);
            skTokenMapper.updateByPrimaryKey(skToken);
            return true;

        }

        // 令牌约等于库存，令牌没有了，就不再卖票，不需要再进入购票主流程去判断库存，判断库存令牌肯定比判断库存效率高
        // sql语句 增加where count判断是查看 是否有数据更新
        // 没加where count的话 不管是否满足都返回一条更新数据
//        int count = skTokenMapperCust.decrease(date, trainCode,decreaseCount);
//        if (count > 0) {
//            return true;
//        }else {
//            return false;
//        }

    }
}
